﻿using System;
using System.Linq;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for creating a new <see cref="Elements.Score"/> in a <see cref="Elements.Project"/>.
    /// </summary>
    /// <remarks>
    /// Input Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     <see cref="ScoreName"/> (<see cref="string"/>)
    /// </term>
    /// <description>
    ///     The name of the new score. It must be unique in the scope of the <see cref="Parent"/> directory.
    ///     Otherwise an execution exception is thrown.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     <see cref="Parent"/> (<see cref="Directory"/>)
    /// </term>
    /// <description>
    ///     The directory in which the new score will be created.
    ///     It must not be <see langword="null"/>.
    /// </description>
    /// </item>
    /// </list>
    /// Output Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     <see cref="CreatedScore"/> (<see cref="Elements.Score"/>)
    /// </term>
    /// <description>
    ///     The score created by this command.
    /// </description>
    /// </item>
    /// </list>
    /// The command can execute if the <see cref="ScoreName"/> is set and unique and the <see cref="Parent"/>
    /// is set to a non-null value.
    /// </remarks>
    public class NewScoreCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="NewScoreCommand"/> instance.
        /// </summary>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack where the command will be put after its execution.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack where the command will be put when it has been undone.
        /// </para>
        /// </param>
        public NewScoreCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<string>("ScoreName", ParameterDirection.In);
            AddParameter<Directory>("Parent", ParameterDirection.In);
            AddParameter<Elements.Score>("CreatedScore", ParameterDirection.Out);

            Name = (string)LocalizationManager.GetValue("Commands.Names.NewScore");
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            if (Parent.Children.Any(child => child.Name == ScoreName))
                throw new ArgumentException("The Parent directory already contains another item with the same name!", "Name");

            var virtualPath = Parent.VirtualPath + "/" + ScoreName;
            var project = Internal.ProjectExplorerItemHelper.GetProject(Parent);
            var scorePart = ((Model.Packaging.ProjectPart)project.AdaptedPart).CreateScorePart(virtualPath);
            CreatedScore = project.Scores.First(score => score.AdaptedPart == scorePart);

            project.IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var project = Internal.ProjectExplorerItemHelper.GetProject(Parent);
            project.AdaptedPart.Package.RestorePart(CreatedScore.AdaptedPart);
            ((Model.Elements.ProjectElement)project.AdaptedElement).Scores.Add(Parent.VirtualPath + "/" + ScoreName, CreatedScore.AdaptedPart.Uri);
            project.Scores[project.Scores.Count - 1] = CreatedScore;
            var vmScore = Internal.ProjectExplorerItemHelper.FindItem(Parent.VirtualPath + "/" + ScoreName, project.GetRootDirectory(Elements.RootDirectory.Scores));          
            vmScore.Parent.Children[vmScore.Parent.Children.IndexOf(vmScore)] = CreatedScore;

            project.IsModified = true;

        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var project = CreatedScore.FindOwner<Elements.Project>();
            ((Model.Elements.ProjectElement)project.AdaptedElement).Scores.Remove(CreatedScore.AdaptedPart.Uri);
            project.AdaptedPart.Package.DeletePart(CreatedScore.AdaptedPart.Uri);

            project.IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the score created by this command.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.Score"/>
        /// </para>
        /// <para>
        /// The score created by this command.<br/>
        /// This property wraps the "CreatedScore" command parameter.
        /// </para>
        /// </value>
        public Elements.Score CreatedScore
        {
            get { return GetParameterValue<Elements.Score>("CreatedScore"); }
            private set { SetParameterValue<Elements.Score>("CreatedScore", value); }
        }

        /// <summary>
        /// Gets or sets the name of the new score.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The name of the new score.
        /// This must be unique in the scope of the <see cref="Parent"/> directory.<br/>
        /// This property wraps the "ScoreName" command parameter.
        /// </para>
        /// </value>
        public string ScoreName
        {
            get { return GetParameterValue<string>("ScoreName"); }
            set { SetParameterValue<string>("ScoreName", value); }
        }

        /// <summary>
        /// Gets or sets the directory in which the new score will be created.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Directory"/>
        /// </para>
        /// <para>
        /// The directory in which the new score will be created.<br/>
        /// This property wraps the "Parent" command parameter.
        /// </para>
        /// </value>
        public Directory Parent
        {
            get { return GetParameterValue<Directory>("Parent"); }
            set { SetParameterValue<Directory>("Parent", value); }
        }

        #endregion
    }
}
